package com.kebabeaters.nakebab.client.ui.widget;

import java.util.List;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
import com.kebabeaters.nakebab.client.bl.ElementDescription;
import com.kebabeaters.nakebab.client.bl.Family;
import com.kebabeaters.nakebab.client.resources.ResourcesFactory;
import com.kebabeaters.nakebab.client.resources.client.KebabResources.MenuStyle;
import com.kebabeaters.nakebab.client.ui.view.widget.CircleLayoutViewWidget.ImageUpdater;
import com.kebabeaters.nakebab.client.utils.Point;

public class CircleLayoutPanel<T extends Widget> extends Composite {

	private class ExpandChildrenAnimation extends Animation {

		private boolean hide = false;

		public void setDirection(boolean hide) {
			this.hide = hide;
		}

		@Override
		protected void onUpdate(double progress) {

			progress = 1 - progress * progress;

			if (!hide) {
				progress = 1 - progress;
			}

			List<T> children = getChildren();

			if (children != null)
				for (T obj : children) {
					obj.getElement().getStyle().setDisplay(Display.BLOCK);
					obj.getElement().getStyle().setOpacity(progress);
				}
		}
	}

	private class CustomAnimation extends Animation {

		private boolean onlyPreview;

		@SuppressWarnings("unused")
		public void setOnlyPreview(boolean onlyPreview)
		{
			this.onlyPreview = onlyPreview;
		}
		
		@Override
		protected void onUpdate(double progress) {

			if (imageUpdater != null) {
				imageUpdater.onUpdate(progress);
			}
			
			if (onlyPreview)
				return;
			
			Double scaled;

			if (progress < 0.125) {
				scaled = 8 * progress;
			} else if (progress > 0.875) {
				scaled = -8 * progress + 8;
			} else
				scaled = 1.0;

			if (scaled != null) {
				updateSelection(scaled);
			}
		}

		@Override
		protected void onComplete() {
			super.onComplete();

			if (onlyPreview)
				return;
			
			handleNextAnimation();

			this.run(animationTime);
		}

		@Override
		protected void onCancel() {
		}
		
		@Override
		public void run(int duration) {
			if (imageUpdater != null)
				imageUpdater.setImage(currentElement.getPreviewImage());

			super.run(duration);
		}
	}

	protected final int radius;
	protected final int range;
	protected final int radiusOffset;

	protected final FlowPanel container = new FlowPanel();
	protected final FocusPanel circle = new FocusPanel();
	protected Double centerLeft;
	protected Double centerTop;
	protected Double centerBottom;
	private int animationTime;
	private List<Family<CircleSubMenu>> displayable;
	private Family<CircleSubMenu> currentFamily;
	private ElementDescription<CircleSubMenu> currentElement;

	private CustomAnimation animation = new CustomAnimation();
	private List<T> children;
	private ImageUpdater imageUpdater;

	public CircleLayoutPanel(int radius, int range, int radiusOffset) {
		this.radius = radius;
		this.range = range;
		this.radiusOffset = radiusOffset;
		this.animationTime = 5000;

		initWidget(container);

		container.getElement().getStyle().setPosition(Position.ABSOLUTE);

		container.add(circle);

		MenuStyle menuStyle = ResourcesFactory.getKebabResources().menuStyle();
		menuStyle.ensureInjected();

		String style = "width: " + radius + "px; height: " + radius
				+ "px; border-radius: " + radius
				+ "px; position: relative; border: 4px solid; "
				+ "border-color: #3060D1;";

		circle.getElement().setAttribute("style", style);

		Image background = new Image(ResourcesFactory.getKebabResources()
				.menuRanksAll().getSafeUri());

		background.setWidth(radius + "px");
		background.setHeight(radius + "px");

		circle.setWidget(background);
	}

	public void setCircleLeftTop(Double centerLeft, Double centerTop) {
		this.centerLeft = centerLeft;
		this.centerTop = centerTop;
		this.centerBottom = null;
		circle.getElement().getStyle()
				.setLeft(centerLeft - radius / 2, Unit.PX);
		circle.getElement().getStyle().setTop(centerTop - radius / 2, Unit.PX);
	}

	public void setCircleLeftBottom(Double centerLeft, Double centerBottom) {
		this.centerLeft = centerLeft;
		this.centerBottom = centerBottom;
		this.centerTop = null;
		circle.getElement().getStyle()
				.setLeft(centerLeft - radius / 2, Unit.PX);
		circle.getElement().getStyle()
				.setBottom(centerBottom - radius / 2, Unit.PX);
	}

	public void setCircleLeftBottomSimple(Double left, Double bottom) {
		circle.getElement().getStyle().setLeft(left, Unit.PX);
		circle.getElement().getStyle().setBottom(bottom, Unit.PX);
	}

	public void setCircleLeftBottom0(Double centerLeft) {
		this.centerLeft = centerLeft;
		this.centerBottom = 0.0;
		this.centerTop = null;
		circle.getElement().getStyle()
				.setLeft(centerLeft - radius / 2, Unit.PX);
		circle.getElement().getStyle().setBottom(0, Unit.PX);
	}

	public void setCircleLeftTop0(Double centerLeft) {
		this.centerLeft = centerLeft;
		this.centerBottom = null;
		this.centerTop = 0.0;
		circle.getElement().getStyle()
				.setLeft(centerLeft - radius / 2, Unit.PX);
		circle.getElement().getStyle().setTop(0, Unit.PX);
	}

	public void setChildren(List<T> children) {
		this.children = children;

		if (centerLeft == null || (centerTop == null && centerBottom == null)) {
			throw new IllegalStateException(
					"Position (left/top/bottom) must be set before adding childs");
		}

		double anglePerWidget = range / (double) (children.size() - 1);

		double topAngle = range / (double) 2;
		double currentAngle = topAngle;

		int index = 0;

		if (children.size() > 0)
			while (currentAngle >= -topAngle) {
				Point<Double> coordinates = calculateCoordinates(radius
						+ radiusOffset, currentAngle);

				T widget = children.get(index);

				widget.getElement().getStyle().setPosition(Position.ABSOLUTE);

				widget.getElement().getStyle()
						.setLeft(this.centerLeft + coordinates.x - 12, Unit.PX);

				double factor = 0;

				if (centerBottom == null && centerTop != null)
					widget.getElement()
							.getStyle()
							.setTop(this.centerTop + coordinates.y + factor + 4,
									Unit.PX);
				else
					widget.getElement()
							.getStyle()
							.setBottom(
									this.centerBottom + coordinates.y + factor
											- 4, Unit.PX);

				widget.getElement().getStyle().setDisplay(Display.NONE);

				container.add(widget);

				currentAngle -= anglePerWidget;
				++index;
			}
	}

	private Point<Double> calculateCoordinates(int x, double alpha) {
		boolean isMinusAngle = false;

		if (alpha < 0) {
			alpha *= -1;
			isMinusAngle = true;
		}

		double alphaRadians = Math.toRadians(alpha);

		double cosAlpha = Math.cos(alphaRadians);

		double t = x * (1 - cosAlpha);
		double k = x - t;
		double h = Math.sqrt(x * x - k * k);

		if (isMinusAngle)
			h *= -1;

		return new Point<Double>(k, h);
	}

	public void setWidth(String width) {
		container.setWidth(width);
	}

	public void setHeight(String height) {
		container.setHeight(height);
	}

	public void enableAnimation(int animationTime,
			List<Family<CircleSubMenu>> displayable) {

		if (displayable == null)
			throw new IllegalArgumentException(
					"Displayable list cannot be null");

		clearSelection();

		// this.currentElement = null;
		// this.currentFamily = null;
		this.animationTime = animationTime;
		this.displayable = displayable;

		handleNextAnimation();

		animation.run(animationTime);
	}

	public void disableAnimation() {
		animation.cancel();
		currentElement = null;
		currentFamily = null;
	}

	public CircleSubMenu setSelectedCircle(CircleSubMenu csm) {
		clearSelection();

		if (csm == null) {
			updateSelection(0.0);
			currentElement = null;
			currentFamily = null;
			return null;
		}

		if (displayable == null) {
			throw new IllegalStateException("Displayable list cannot be null");
		} else {
			loop: for (Family<CircleSubMenu> family : displayable) {
				if (family.getMainElement().getObj() == csm) {
					currentFamily = family;

					currentElement = family.getMainElement();
					break loop;
				} else {
					if (family.getChildren() != null)
						for (ElementDescription<CircleSubMenu> element : family
								.getChildren()) {
							if (element.getObj() == csm) {
								currentFamily = family;
								currentElement = element;
								break loop;
							}
						}
				}
			}

			if (currentElement != null) {
				circle.setWidget(currentElement.getLayoutImage());
				updateSelection(1.0);
			}

			return currentElement.getObj();
		}
	}

	private void clearSelection() {
		if (displayable != null)
			for (Family<CircleSubMenu> f : displayable) {
				f.getMainElement().getObj().setSelection(0);

				if (f.getChildren() != null)
					for (ElementDescription<CircleSubMenu> e : f.getChildren()) {
						e.getObj().setSelection(0);
					}
			}
	}

	private void handleNextAnimation() {

		if (displayable == null) {
			throw new IllegalStateException("Displayable list cannot be null");
		} else if (currentFamily == null || currentElement == null) {
			currentFamily = displayable.get(0);
			currentElement = currentFamily.getMainElement();
		} else if (currentFamily.getMainElement().getObj() == currentElement
				.getObj()) {
			// if (currentFamily.getChildren() != null
			// && currentFamily.getChildren().size() > 0)
			// currentElement = currentFamily.getChildren().get(0);
			// else {
			currentFamily = getNextChild(displayable, currentFamily, true);
			currentElement = currentFamily.getMainElement();
		} else {
			// currentElement = getNextChild(currentFamily.getChildren(),
			// currentElement, false);
			//
			// if (currentElement == null) {
			currentFamily = getNextChild(displayable, currentFamily, true);
			currentElement = currentFamily.getMainElement();
			// }
		}

		currentElement.getLayoutImage().setWidth(radius + "px");
		currentElement.getLayoutImage().setHeight(radius + "px");

		circle.setWidget(currentElement.getLayoutImage());
	}

	private void updateSelection(double progress) {
		if (currentElement != null) {
			currentElement.getLayoutImage().getElement().getStyle()
					.setOpacity(progress);
			currentElement.getObj().setSelection(progress);
		}
	}

	private <K> K getNextChild(List<K> container, K element, boolean cycle) {

		int index = container.indexOf(element);

		if (index >= 0) {
			if (index == container.size() - 1) {
				if (cycle)
					return container.get(0);
				else
					return null;
			} else
				return container.get(index + 1);
		} else
			return container.get(0);
	}

	public void expandChildrenNow() {
		for (int i = 0; i < container.getWidgetCount(); ++i) {
			container.getWidget(i).getElement().getStyle()
					.setDisplay(Display.BLOCK);
		}
	}

	public void expandChildren() {
		ExpandChildrenAnimation expandChildrenAnimation = new ExpandChildrenAnimation();
		expandChildrenAnimation.setDirection(false);
		expandChildrenAnimation.run(1000);
	}

	public void hideChildren() {
		ExpandChildrenAnimation expandChildrenAnimation = new ExpandChildrenAnimation() {

			@Override
			protected void onComplete() {
				super.onComplete();

				List<T> children = getChildren();

				if (children != null)
					for (T obj : children) {
						obj.getElement().getStyle().setDisplay(Display.NONE);
					}
			}
		};
		expandChildrenAnimation.setDirection(true);
		expandChildrenAnimation.run(1000);
	}

	public List<T> getChildren() {
		return children;
	}

	public HasClickHandlers getClickHandler() {
		return circle;
	}

	public void setAnimationUpdater(ImageUpdater imageUpdater) {
		this.imageUpdater = imageUpdater;
	}
}
